#include "Obj.h"

using namespace DirectX;

Obj::Obj(void)
{
}


Obj::~Obj(void)
{
}


void Obj::Destroy()
{
  m_Attributes.clear();
  m_Indices.clear();
  m_Materials.clear();
  m_Vertices.clear();
}

HRESULT Obj::Create( const std::string strFilename )
{
  /*m_Vertices.clear();
  m_Indices.clear();*/

  HRESULT hr;

  Destroy();

  hr = LoadGeometryFromOBJ(strFilename);
  if(FAILED(hr))
    return hr;

  //Todo: pozostala czesc

  return S_OK;
}

HRESULT Obj::LoadGeometryFromOBJ(const std::string strFileName)
{
  std::string strMaterialFilename, strTextureFilename, strMaterialName;
  WCHAR wstr[MAX_PATH];
  char str[MAX_PATH];
  HRESULT hr;

  unsigned int index = strFileName.find_last_of('/');
  std::string folder = strFileName.substr(0, index + 1);

  // Find the file

  // Create temporary storage for the input data. Once the data has been loaded into
  // a reasonable format we can create a D3DXMesh object and load it with the mesh data.

  std::vector<XMFLOAT3> Positions;
  std::vector<XMFLOAT2> TexCoords;
  std::vector<XMFLOAT3> Normals;

  // The first subset uses the default material
  Material* pMaterial = new Material();
  if( pMaterial == NULL )
    return E_OUTOFMEMORY;

  /*InitMaterial( pMaterial );
  wcscpy_s( pMaterial->strName, MAX_PATH - 1, L"default" );
  m_Materials.Add( pMaterial );
  */
  DWORD dwCurSubset = 0;

  // File input
  WCHAR strCommand[256] = {0};
  std::wifstream InFile( strFileName );

  if( !InFile )
    return E_FAIL;

  for(; ; )
  {
    InFile >> strCommand;
    if( !InFile )
      break;

    if( 0 == wcscmp( strCommand, L"#" ) )
    {
      // Comment
    }
    else if( 0 == wcscmp( strCommand, L"v" ) )
    {
      // Vertex Position
      float x, y, z;
      InFile >> x >> y >> z;
      Positions.push_back( XMFLOAT3( x, y, z ) );
    }
    else if( 0 == wcscmp( strCommand, L"vt" ) )
    {
      // Vertex TexCoord
      float u, v;
      InFile >> u >> v;
      TexCoords.push_back( XMFLOAT2( u, v ) );
    }
    else if( 0 == wcscmp( strCommand, L"vn" ) )
    {
      // Vertex Normal
      float x, y, z;
      InFile >> x >> y >> z;
      Normals.push_back(XMFLOAT3( x, y, z ) );
    }
    else if( 0 == wcscmp( strCommand, L"f" ) )
    {
      // Face
      UINT iPosition, iTexCoord, iNormal;
      Vertex vertex;

      for( UINT iFace = 0; iFace < 3; iFace++ )
      {
        ZeroMemory( &vertex, sizeof( Vertex ) );

        // OBJ format uses 1-based arrays
        InFile >> iPosition;
        vertex.pos = Positions[ iPosition - 1 ];

        if( '/' == InFile.peek() )
        {
          InFile.ignore();

          if( '/' != InFile.peek() )
          {
            // Optional texture coordinate
            InFile >> iTexCoord;
            vertex.tex0 = TexCoords[ iTexCoord - 1 ];
          }

          if( '/' == InFile.peek() )
          {
            InFile.ignore();

            // Optional vertex normal
            InFile >> iNormal;
            vertex.norm = Normals[ iNormal - 1 ];
          }
        }

        // If a duplicate vertex doesn't exist, add this vertex to the Vertices
        // list. Store the index in the Indices array. The Vertices and Indices
        // lists will eventually become the Vertex Buffer and Index Buffer for
        // the mesh.
        DWORD index = AddVertex( iPosition, &vertex );
        if ( index == (DWORD)-1 )
          return E_OUTOFMEMORY;

        m_Indices.push_back( index );
      }
      m_Attributes.push_back( dwCurSubset );
    }
    else if( 0 == wcscmp( strCommand, L"mtllib" ) )
    {
      // Material library
      WCHAR tmp[MAX_PATH] = {0};
      strMaterialFilename = "";
      InFile >> tmp;
      strMaterialFilename = WcharToString(tmp);      
    }
    else if( 0 == wcscmp( strCommand, L"usemtl" ) )
    {
      // Material
      WCHAR strName[MAX_PATH] = {0};
      InFile >> strName;

      strMaterialName = WcharToString(strName);
      
      std::map<std::string, Material*>::iterator it = m_Materials.find(strMaterialName);
      bool bFound = it != m_Materials.end();

      if( !bFound )
      {
        pMaterial = new Material();
        if( pMaterial == NULL )
          return E_OUTOFMEMORY;       

        InitMaterial( pMaterial );
        m_Materials[strMaterialName] = pMaterial;
      }
    }
    else
    {
      // Unimplemented or unrecognized command
    }

    InFile.ignore( 1000, '\n' );
  }

  // Cleanup
  InFile.close();
  //    DeleteCache();

  // If an associated material file was found, read that in as well.
  if( strMaterialFilename[0] )
  {
    if(FAILED( LoadMaterialsFromMTL( folder + std::string(strMaterialFilename ))))
      return E_FAIL;
  }

  //m_textureName = strTextureFilename;
  return S_OK;
}


DWORD Obj::AddVertex( UINT hash, Vertex* pVertex )
{
  // If this vertex doesn't already exist in the Vertices list, create a new entry.
  // Add the index of the vertex to the Indices list.
  bool foundInList = false;
  int index = -1;

  // Since it's very slow to check every element in the vertex list, a hashtable stores
  // vertex indices according to the vertex position's index as reported by the OBJ file
  for(std::vector<Vertex>::iterator it = m_Vertices.begin(); it != m_Vertices.end(); ++it)
  {
    ++index;
    if(*it == *pVertex)
    {
      foundInList = true;
      break;
    }
  }

  if(!foundInList)
  {
    index = m_Vertices.size();
    m_Vertices.push_back(*pVertex);
    return index;
  }

  bool exists = m_map[index] != nullptr;

  if(exists)
  {
    if(*m_map[index] == *pVertex)
      return index;
  }
  //std::list<Vertex> l;
  //
  //m_Vertices.push_back(*pVertex);
  //index = m_Vertices.size() - 1;
  //m_map[index] = pVertex;
  //
  /*
  if(m_Vertices.size() < hash)
  m_Vertices.push_back(*pVertex);

  iterator = m_map.find(hash);
  if(iterator != m_map.end())
  return hash;

  m_map[hash] = pVertex;
  */
  return index;
}


unsigned int Obj::GetNumIndices()
{
  return this->m_Indices.size();
}

unsigned int Obj::GetNumVertices()
{
  return this->m_Vertices.size();
}

unsigned Obj::GetNumMaterials()
{
  return this->m_Materials.size();
}

Material* Obj::GetMaterials()
{
  unsigned num = this->GetNumMaterials();
  if(num == 0)
    return 0;

  Material* list = new Material[num];
  unsigned i = 0;
  for(std::map<std::string, Material*>::iterator it = m_Materials.begin(); it != m_Materials.end(); ++it)
  {
    list[i++] = *(it->second);
  }

  return list;
}

WORD* Obj::GetIndices()
{
  unsigned int size = m_Indices.size();
  if(size == 0)
    return 0;

  WORD* indices = new WORD[size];
  for(int i = 0; i < size; ++i)
  {
    indices[i] = m_Indices[i];
  }

  return indices;
}

Vertex* Obj::GetVertices()
{
  unsigned int size = m_Vertices.size();
  if(size == 0)
    return 0;

  Vertex* vertices = new Vertex[size];

  for(int i = 0; i < size; ++i)
  {
    vertices[i] = m_Vertices[i];
  }

  return vertices;
}

HRESULT Obj::LoadMaterialsFromMTL(std::string filename)
{
  HRESULT hr;

  // Set the current directory based on where the mesh was found
  WCHAR strTmp[MAX_PATH] = {0};

  // Find the file

  // File input
  WCHAR strCommand[256] = {0};
  std::wifstream InFile( filename.c_str() );
  if( !InFile )
    return E_FAIL;

  // Restore the original current directory   

  Material* pMaterial = NULL;

  for(; ; )
  {
    InFile >> strCommand;
    if( !InFile )
      break;

    if( 0 == wcscmp( strCommand, L"newmtl" ) )
    {
      // Switching active materials
      WCHAR strName[MAX_PATH] = {0};
      InFile >> strName;
      std::string tmp = WcharToString(strName);

      pMaterial = NULL;

      std::map<std::string, Material*>::iterator it = m_Materials.find(tmp);
      if(it != m_Materials.end())
        pMaterial = it->second;
      else
      {
        pMaterial = new Material();
        this->InitMaterial(pMaterial);
        m_Materials[tmp] = pMaterial;
      }
    }

    // The rest of the commands rely on an active material
    if( pMaterial == NULL )
      continue;

    if( 0 == wcscmp( strCommand, L"#" ) )
    {
      // Comment
    }
    else if( 0 == wcscmp( strCommand, L"Ka" ) )
    {
      //// Ambient color
      float r, g, b;
      InFile >> r >> g >> b;
      pMaterial->Ambient = XMFLOAT3( r, g, b );
    }
    else if( 0 == wcscmp( strCommand, L"Kd" ) )
    {
      //// Diffuse color
      float r, g, b;
      InFile >> r >> g >> b;
      pMaterial->Diffuse = XMFLOAT3( r, g, b );
    }
    else if( 0 == wcscmp( strCommand, L"Ks" ) )
    {
      // Specular color
      float r, g, b;
      InFile >> r >> g >> b;
      pMaterial->Specular = XMFLOAT3( r, g, b );
    }
    else if( 0 == wcscmp( strCommand, L"d" ) ||
      0 == wcscmp( strCommand, L"Tr" ) )
    {
      // Alpha
      InFile >> pMaterial->Alpha;
    }
    else if( 0 == wcscmp( strCommand, L"Ns" ) )
    {
      // Shininess
      InFile >> pMaterial->Shininess;            
    }
    else if( 0 == wcscmp( strCommand, L"illum" ) )
    {
      // Specular on/off
      int illumination;
      InFile >> illumination;
      pMaterial->IsSpecular = ( illumination == 2 );
    }
    else if( 0 == wcscmp( strCommand, L"map_Kd" ) )
    {
      // Texture
      InFile >> strTmp;
      pMaterial->TextureName = WcharToString(strTmp);
      int start = pMaterial->TextureName.find_last_of('\\') + 1;

      pMaterial->TextureName = pMaterial->TextureName.substr(start);
    }

    else
    {
      // Unimplemented or unrecognized command
    }

    InFile.ignore( 1000, L'\n' );
  }

  InFile.close();
}

void Obj::InitMaterial( Material* pMaterial )
{
  pMaterial->Alpha = 1.0f;
  pMaterial->Ambient = XMFLOAT3(0.2f, 0.2f, 0.2f);
  pMaterial->Diffuse = XMFLOAT3(0.8f, 0.8f, 0.8f);
  pMaterial->IsSpecular = false;
  pMaterial->Shininess = 0;
  pMaterial->Specular = XMFLOAT3(1.0f, 1.0f, 1.0f);
  pMaterial->Texture = NULL;
  pMaterial->TextureName = "";
}